package scanner;

import compiler.Properties;

import parser.GrammarSymbols;
import util.Arquivo;

/**
 * Scanner class
 * @version 2010-september-04
 * @discipline Compiladores
 * @author Gustavo H P Carvalho
 * @email gustavohpcarvalho@ecomp.poli.br
 */
public class Scanner {

	// The file object that will be used to read the source code
	private Arquivo file;
	// The last char read from the source code
	private char currentChar;
	// The kind of the current token
	private int currentKind;
	// Buffer to append characters read from file
	private StringBuffer currentSpelling;
	// Current line and column in the source file
	private int line, column;
	
	/**
	 * Default constructor
	 */
	public Scanner() {
		this.file = new Arquivo(Properties.sourceCodeLocation);		
		this.line = 0;
		this.column = 0;
		this.currentChar = this.file.readChar();
	}
	
	public Scanner(String file) {
		this.file = new Arquivo(file);		
		this.line = 0;
		this.column = 0;
		this.currentChar = this.file.readChar();
	}
	
	/**
	 * Returns the next token
	 * @return
	 * @throws LexicalException  
	 */ //TODO
	public Token getNextToken() throws LexicalException {
		// Initializes the string buffer
		currentSpelling = new StringBuffer();
		// Ignores separators
		scanSeparator();
		// Clears the string buffer
		currentSpelling.setLength(0);
		// Scans the next token
		currentKind = scanToken();
		// Creates and returns a token for the lexema identified
		return new Token(currentKind,currentSpelling.toString(), line, column);
	}
	
	/**
	 * Returns if a character is a separator
	 * @param c
	 * @return
	 */
	private boolean isSeparator(char c) {
		if ( c == '#' || c == ' ' || c == '\n' || c == '\t' ) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Reads (and ignores) a separator
	 * @throws LexicalException
	 */ //TODO
	private void scanSeparator() throws LexicalException {
		// If it is a comment line
			// Gets next char
			// Reads characters while they are graphics or '\t'
			// A command line should finish with a \n
		while(isSeparator(currentChar)){
			if(currentChar == '#'){
				while(currentChar != '\n'){
					getNextChar();
				}
				getNextChar();
			}else
				getNextChar();
		}
	}
	
	/**
	 * Gets the next char
	 */
	private void getNextChar() {
		// Appends the current char to the string buffer
		this.currentSpelling.append(this.currentChar);
		// Reads the next one
		this.currentChar = this.file.readChar();
		// Increments the line and column
		this.incrementLineColumn();
	}
	
	/**
	 * Increments line and column
	 */
	private void incrementLineColumn() {
		// If the char read is a '\n', increments the line variable and assigns 0 to the column
		if ( this.currentChar == '\n' ) {
			this.line++;
			this.column = 0;
		// If the char read is not a '\n' 
		} else {
			// If it is a '\t', increments the column by 4
			if ( this.currentChar == '\t' ) {
				this.column = this.column + 4;
			// If it is not a '\t', increments the column by 1
			} else {
				this.column++;
			}
		}
	}
	
	/**
	 * Returns if a char is a digit (between 0 and 9)
	 * @param c
	 * @return
	 */
	private boolean isDigit(char c) {
		if ( c >= '0' && c <= '9' ) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns if a char is a letter (between a and z or between A and Z)
	 * @param c
	 * @return
	 */
	private boolean isLetter(char c) {
		if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns if a char is a graphic (any ASCII visible character)
	 * @param c
	 * @return
	 
	private boolean isGraphic(char c) {
		if ( c >= ' ' && c <= '~' ) {
			return true;
		} else {
			return false;
		}
	}*/
	
	/**
	 * Scans the next token
	 * Simulates the DFA that recognizes the language described by the lexical grammar
	 * @return
	 * @throws LexicalException
	 */ //TODO
	private int scanToken() throws LexicalException {
		// The initial automata state is 0
		// While loop to simulate the automata
		int kind = -1;
		
		if(currentChar == '\0') return GrammarSymbols.EOT;
		else if (isLetter(currentChar)){ //reconhece palavras reservadas ou identificadores.
			while(isLetter(currentChar)){
				getNextChar();
			}
			String string = currentSpelling.toString();
			if(string.equals("int")) return GrammarSymbols.INT;
			else if (string.equals("void")) return GrammarSymbols.VOID;
			else if (string.equals("double")) return GrammarSymbols.DOUBLE;
			else if (string.equals("boolean")) return GrammarSymbols.BOOLEAN;
			else if (string.equals("false")) return GrammarSymbols.FALSE;
			else if (string.equals("true")) return GrammarSymbols.TRUE;
			else if (string.equals("if")) return GrammarSymbols.IF;
			else if (string.equals("else")) return GrammarSymbols.ELSE;
			else if (string.equals("break")) return GrammarSymbols.BREAK;
			else if (string.equals("continue")) return GrammarSymbols.CONTINUE;
			else if (string.equals("return")) return GrammarSymbols.RETURN;
			else if (string.equals("println")) return GrammarSymbols.PRINTLN;
			else if (string.equals("while")) return GrammarSymbols.WHILE;
			
			// Aqui eu ja garanto que o token ser� um identificador
			else if(isDigit(currentChar) || isLetter(currentChar)){
				while(isDigit(currentChar)|| isLetter(currentChar))
					getNextChar();
			}
			return GrammarSymbols.ID;
		
			// Aqui eu teste se � um numero.
		}else if(isDigit(currentChar)){
			while(isDigit(currentChar)){
				getNextChar();
			}
			if(currentChar == '.'){ // Testa se � um numero de ponto flutuante.
				getNextChar();
				if(!isDigit(currentChar)) 
					throw new LexicalException("Digit was expected!",currentChar,line, column);
				else{
					while(isDigit(currentChar)) 
						getNextChar();
					return GrammarSymbols.NUMBER;
				}
			}else
				return GrammarSymbols.NUMBER;
		}
		else if(currentChar == '{') kind = GrammarSymbols.LBRACKET;
		else if(currentChar == '}') kind = GrammarSymbols.RBRACKET;
		else if(currentChar == ';') kind = GrammarSymbols.SEMICOLON;
		else if(currentChar == ',') kind = GrammarSymbols.COMMA;
		else if(currentChar == '(') kind = GrammarSymbols.LPAR;
		else if(currentChar == ')') kind = GrammarSymbols.RPAR;
		
		// Aqui eu testo os conectivos de express�es booleanas.
		else if(currentChar == '!'){ // !=
			getNextChar();
			if(currentChar == '=') kind = GrammarSymbols.NOTEQUAL;
			else throw new LexicalException("Symbol '=' expected!", currentChar, line, column);
		}else if (currentChar == '='){ // ==
			getNextChar();
			if (currentChar == '=') kind = GrammarSymbols.EQUAL;
			else return GrammarSymbols.ASSIGN;
		}else if (currentChar == '<'){ // <=
			getNextChar();
			if (currentChar == '=') kind = GrammarSymbols.LESSEREQUALTHAN;
			else return GrammarSymbols.LESSERTHAN;
		}else if (currentChar == '>'){ // >=
			getNextChar();
			if (currentChar == '=') kind = GrammarSymbols.GREATEREQUALTHAN;
			else return GrammarSymbols.GREATERTHAN;
		}
		
		// Aqui eu testo os operadores
		else if(currentChar == '*') kind = GrammarSymbols.MULT;
		else if(currentChar == '+') kind = GrammarSymbols.PLUS;
		else if(currentChar == '-') kind = GrammarSymbols.MINUS;
		else if(currentChar == '/') kind = GrammarSymbols.DIV;
		else
			throw new LexicalException("Caracter Inv\u00e1lido!",currentChar,line,column);
		
		getNextChar();
		return kind;
	}
	
	}
